/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 * */

// AmzServer - Tototoy's server model.
// Author: tonyjobmails@gmail.com (tonyhack).
//
// Define class MessagePacket.
//
// MessagePacket uses to packet/unpacket message.
//

#include "common.h"
#include "packet_define.h"
#include "logging.h"
#include "compress_none.h"
#include "encrypt_none.h"
#include "message_packet.h"

namespace amz {

// Factory of compress/encrypt tools.
static CompressInterface *compress_tools[COMPRESS_TYPE_MAX] = {
  new CompressNone,
};
static EncryptInterface *encrypt_tools[ENCRYPT_TYPE_MAX] = {
  new EncryptNone,
};

MessagePacket::MessagePacket(CompressType compress, EncryptType encrypt) {
  this->compress_interface_ = compress_tools[compress];
  this->encrypt_interface_ = encrypt_tools[encrypt];
}

MessagePacket::~MessagePacket() {}

int MessagePacket::Packet(const char *in, int size, char *out, int max_size) {
  if(size == 0) {
    // Empty message is not allow.
    return packet::PACKET_ERROR_IMPORT_SIZE;
  }
  int encrypt_need_size = this->encrypt_interface_->CalculateEncryptSize(size);
  if(this->compress_interface_->CalculateCompressSize(encrypt_need_size) +
         packet::kPacketHeadSize >= (uint32)max_size) {
    return packet::PACKET_ERROR_SPACE_NOT_ENOUGH;
  }
  char *data = out + packet::kPacketHeadSize;
  // Encrypt.
  if(this->buffer_.PreWrite(encrypt_need_size) == false) {
    LOG(ERROR) << "failed to Packet, buffer_ size is not enough.";
    return packet::PACKET_ERROR_UNKNOWN;
  }
  int encrypt_size = this->encrypt_interface_->Encrypt(in, size,
         this->buffer_.HeadPointer(), encrypt_need_size);
  // Compress.
  int compress_size = this->compress_interface_->Compress(
         this->buffer_.HeadPointer(), encrypt_size, data, max_size);
  // Head.
  *(uint32*)out = SET_PACKET_ZIP_TYPE(this->compress_interface_->GetType()) |
         SET_PACKET_ENC_TYPE(this->encrypt_interface_->GetType()) | compress_size;

  return compress_size + packet::kPacketHeadSize;
}

int MessagePacket::Unpacket(const char *in, int size, char *out, int max_size) {
  // Check "in" is whole packet.
  if((uint32)size < packet::kPacketHeadSize) {
    // less than a head.
    return packet::PACKET_ERROR_PART_PACKET;
  }
  uint32 head = *(uint32*)in;
  int message_size = (int)(head & packet::kPacketLengthMask);
  if(message_size > size) {
    // less than a packet.
    return packet::PACKET_ERROR_PART_PACKET;
  }
  // Get compress and encrypt type.
  CompressType compress_type = (CompressType)GET_PACKET_ZIP_TYPE(head);
  if(compress_type >= COMPRESS_TYPE_MAX) {
    LOG(ERROR) << "Compress type error.";
    return packet::PACKET_ERROR_COMPRESS_TYPE;
  }
  EncryptType encrypt_type = (EncryptType)GET_PACKET_ENC_TYPE(head);
  if(encrypt_type >= ENCRYPT_TYPE_MAX) {
    LOG(ERROR) << "Encrypt type error.";
    return packet::PACKET_ERROR_ENCRYPT_TYPE;
  }
  // Get compress and encrypt instance
  CompressInterface *compress_instance = compress_tools[compress_type];
  EncryptInterface *encrypt_instance = encrypt_tools[encrypt_type];
  // Get part of data pointer.
  const char *data = in + packet::kPacketHeadSize;
  // Check max_size uncompress enough.
  int uncompress_need_size = compress_instance->CalculateUncompressSize(message_size);
  if(uncompress_need_size > max_size) {
    return packet::PACKET_ERROR_SPACE_NOT_ENOUGH;
  }
  // Uncompress.
  if(this->buffer_.PreWrite(uncompress_need_size) == false) {
    LOG(ERROR) << "failed to Unpacket, buffer_ size is not enough.";
    return packet::PACKET_ERROR_UNKNOWN;
  }
  int uncompress_size = compress_instance->Uncompress(data, message_size,
         this->buffer_.HeadPointer(), uncompress_need_size);
  // Check max_size unencrypt enough.
  int unencrypt_need_size = encrypt_instance->CalculateUnencryptSize(uncompress_size);
  if(unencrypt_need_size > max_size) {
    return packet::PACKET_ERROR_SPACE_NOT_ENOUGH;
  }
  // Unencrypt.
  return encrypt_instance->Unencrypt(this->buffer_.HeadPointer(), uncompress_size, out, max_size);
}

}  // namespace amz

